Since 2015, JavaScript has improved immensely.
It’s much more pleasant to use it now than ever.
In this article, we’ll look at metaprogramming with JavaScript proxies.
Revocable Proxies
We can create revocable proxies with ES6.
To do that, we call the Proxy.revocable
method with the target
and handler
arguments.
For example, we can write:
const target = {};
const handler = {
get(target, propKey, receiver) {
return 'foo';
},
has(target, propKey) {
return true;
}
};
const {
proxy,
revoke
} = Proxy.revocable(target, handler);
The Proxy.revocable
function takes the target
object which we want to change the behavior of.
handler
is an object which has various methods to let us control the behavior of target
.
It returns an object with the proxy
object and the revoke
function to revoke the proxy.
We can call revoke
to stop using the proxy.
For example, if we have:
const target = {};
const handler = {
get(target, propKey, receiver) {
return target[propKey];
},
has(target, propKey) {
return true;
}
};
const {
proxy,
revoke
} = Proxy.revocable(target, handler);
proxy.foo = 'bar';
console.log(proxy.foo);
revoke();
console.log(proxy.foo);
Then the 2nd console log will give us an error since the proxy has been revoked with the revoke
function.
Therefore, we get ‘Uncaught TypeError: Cannot perform ‘get’ on a proxy that has been revoked’.
Proxies as Prototypes
We can use proxies as a prototype of an object.
For example, we can write:
const target = {};
const handler = {
get(target, propKey, receiver) {
return target[propKey];
},
has(target, propKey) {
return true;
}
};
const proto = new Proxy(target, handler);
const obj = Object.create(proto);
to use the Object.create
method to create a proxy object and use that as the prototype.
Forwarding Intercepted Operations
We can forward intercepted operations with proxies.
For example, we can write:
const target = {};
const handler = {
deleteProperty(target, propKey) {
return delete target[propKey];
},
has(target, propKey) {
return propKey in target;
},
}
const proto = new Proxy(target, handler);
to add the forward the delete
operation and the in
operation.
We just do the same thing that we expect without the proxy within the handler
methods.
Wrapping an Object Affects this
Since the handler
is an object, it has its own value of this
.
Therefore, if we have:
const target = {
foo() {
console.log(this === target)
console.log(this === proxy)
}
};
const handler = {};
const proxy = new Proxy(target, handler);
proxy.foo()
Then the first console log is false
and the 2nd is true
since we called foo
on proxy
.
On the other hand, if we have:
const target = {
foo() {
console.log(this === target)
console.log(this === proxy)
}
};
const handler = {};
const proxy = new Proxy(target, handler);
target.foo()
Then the first console log is true
and the 2nd is false
.
Objects that can’t be Wrapped Transparently
If an object has some private data, then a proxy can’t wrap the object transparently.
For example, if we have:
const _name = new WeakMap();
class Animal {
constructor(name) {
_name.set(this, name);
}
get name() {
return _name.get(this);
}
}
Then if we have:
const mary = new Animal('mary');
const proxy = new Proxy(mary, {});
console.log(proxy.name);
We get that the name
property in undefined
.
This is because the name
is stored in the WeakMap instead of as a direct property of this
itself.
this
is the proxy so it doesn’t have the name
property.
Conclusion
Proxies can be revocable, and the value of this
are different between the proxy and the original object.